package db_utils

import (
	"database/sql"
	"dgo/goutils/utils"
	"fmt"
)

type DBHelper struct {
	dbconnid     string
	datasourceid string
	dbchangedna  *int32
	dbdna        int32
	db           *sql.DB
	transtx      *sql.Tx
	lastDBErr    error
	queryfunc    func(query string, args ...interface{}) (*sql.Rows, error)
	execfunc     func(query string, args ...interface{}) (sql.Result, error)
	OnSQLFunc    func(err error, query string, args ...interface{})
}

func null_onsqlfunc(err error, query string, args ...interface{}) {

}

func NewDBHelper(dbconnid, datasourceid string) *DBHelper {
	rval := &DBHelper{dbconnid: dbconnid, datasourceid: datasourceid, OnSQLFunc: null_onsqlfunc}
	return rval
}

func NewDBHelperEx(datasourceid string) *DBHelper {
	rval := &DBHelper{dbconnid: datasourceid, datasourceid: datasourceid, OnSQLFunc: null_onsqlfunc}
	return rval
}

func (this *DBHelper) GetDriverType() string {
	return GetDatasourceDriverName(this.datasourceid)
}

func (this *DBHelper) PrepareDB() error {
	if this.db == nil || this.dbdna != *this.dbchangedna {
		db, changedna, err := OpenDBConnWithChangeDNA(this.dbconnid)
		if err != nil {
			return err
		}
		this.dbchangedna = changedna
		this.dbdna = *changedna
		this.db = db
		this.resetfunc()
	}
	return nil
}

func (this *DBHelper) resetfunc() {
	this.queryfunc = this.db.Query
	this.execfunc = this.db.Exec
}

func (this *DBHelper) CheckBeginTrans() error {
	if this.transtx == nil {
		return this.BeginTrans()
	}
	return nil
}

func (this *DBHelper) BeginTrans() error {
	err := this.PrepareDB()
	if err != nil {
		return err
	}
	tx, err := this.db.Begin()
	if err != nil {
		return err
	}
	this.transtx = tx
	this.execfunc = this.transtx.Exec
	this.queryfunc = this.transtx.Query
	return nil
}

func (this *DBHelper) Commit() error {
	this.resetfunc()
	if this.transtx == nil {
		return fmt.Errorf("尚未开启事务!")
	}
	return this.transtx.Commit()
}

func (this *DBHelper) Rollback() error {
	this.resetfunc()
	if this.transtx == nil {
		return fmt.Errorf("尚未开启事务!")
	}
	err := this.transtx.Rollback()
	this.transtx = nil
	return err
}

func (this *DBHelper) AutoDoneTrans() error {
	if this.transtx == nil {
		return nil
	}
	if this.lastDBErr != nil {
		return this.Rollback()
	} else {
		return this.Commit()
	}
}

func (this *DBHelper) ExecuteEx(query string, args ...interface{}) (int64, error) {
	ret, err := this.Execute(query, args...)
	if err != nil {
		return 0, err
	}
	cnt, _ := ret.RowsAffected()
	return cnt, err
}

func (this *DBHelper) Execute(query string, args ...interface{}) (sql.Result, error) {
	err := this.PrepareDB()
	if err != nil {
		return nil, err
	}
	rval, err := this.execfunc(query, args...)
	if err != nil {
		this.lastDBErr = err
	}
	this.OnSQLFunc(err, query, args...)
	return rval, err
}

func (this *DBHelper) GetDB() *sql.DB {
	return this.db
}

func (this *DBHelper) Query(query string, args ...interface{}) (*sql.Rows, error) {
	err := this.PrepareDB()
	if err != nil {
		return nil, err
	}
	rval, err := this.queryfunc(query, args...)
	if err != nil {
		this.lastDBErr = err
	}
	this.OnSQLFunc(err, query, args...)
	return rval, err
}

func (this *DBHelper) GetColumns(table string) (columns []*sql.ColumnType, err error) {
	err = this.PrepareDB()
	if err != nil {
		return
	}
	sql := fmt.Sprintf("select * from %s where 1=0", table)
	rows, e0 := this.Query(sql)
	if e0 != nil {
		err = e0
		return
	}
	defer rows.Close()
	return rows.ColumnTypes()
}

func (this *DBHelper) Close() error {
	this.db = nil
	return nil
}

func (this *DBHelper) Ping() error {
	err := this.PrepareDB()
	if err != nil {
		return err
	}
	return this.db.Ping()
}

func (this *DBHelper) Exists(sql string, args ...interface{}) (bool, error) {
	err := this.PrepareDB()
	if err != nil {
		return false, err
	}
	rows, err := this.Query(sql, args...)
	if err != nil {
		return false, err
	}

	defer rows.Close()

	return rows.Next(), nil
}

func (this *DBHelper) GetFirstValAsInt64(sql string, def int64, args ...interface{}) (int64, error) {
	err := this.PrepareDB()
	if err != nil {
		return def, err
	}
	rows, err := this.Query(sql, args...)
	if err != nil {
		return def, err
	}

	defer rows.Close()

	var val SqlValue
	if rows.Next() {
		if err := rows.Scan(&val.Value); err != nil {
			return def, err
		}
		return val.AsInt64(def), nil
	}
	return def, nil
}

func (this *DBHelper) GetFirstValAsString(sql string, def string, args ...interface{}) (string, error) {
	err := this.PrepareDB()
	if err != nil {
		return def, err
	}
	rows, err := this.Query(sql, args...)
	if err != nil {
		return def, err
	}

	defer rows.Close()

	var val SqlValue
	if rows.Next() {
		if err := rows.Scan(&val.Value); err != nil {
			return def, err
		}
		return val.AsString(def), nil
	}
	return def, nil
}

func FindColumnByName(columns []*sql.ColumnType, colname string) *sql.ColumnType {
	for i := 0; i < len(columns); i++ {
		if utils.CompareIgnoreCase(columns[i].Name(), colname) == 0 {
			return columns[i]
		}
	}
	return nil
}
